Skip to main content
Glama
northernvariables

FedMCP - Federal Parliamentary Information

page.tsx16.7 kB
'use client'; import { useState, useMemo } from 'react'; import { useQuery } from '@apollo/client'; import { useTranslations, useLocale } from 'next-intl'; import { GET_RECENT_DEBATES, GET_QUESTION_PERIOD_DEBATES } from '@/lib/queries'; import { DebateCard } from '@/components/debates/DebateCard'; import { DebatesCalendar } from '@/components/debates/DebatesCalendar'; import { Header } from '@/components/Header'; import { Footer } from '@/components/Footer'; import { Search, Filter, Calendar, X } from 'lucide-react'; export default function DebatesPage() { const t = useTranslations('debates'); const locale = useLocale(); // State const [filter, setFilter] = useState<'all' | 'debates' | 'committee' | 'qp'>('all'); const [limit] = useState(20); const [selectedStartDate, setSelectedStartDate] = useState<Date | null>(null); const [selectedEndDate, setSelectedEndDate] = useState<Date | null>(null); const [calendarViewMode, setCalendarViewMode] = useState<1 | 3>(1); const [searchQuery, setSearchQuery] = useState(''); // Handlers const handleDateRangeSelect = (start: Date | null, end: Date | null) => { setSelectedStartDate(start); setSelectedEndDate(end); }; const handleViewModeChange = (mode: 1 | 3) => { setCalendarViewMode(mode); }; const formatDateForQuery = (date: Date | null) => { if (!date) return undefined; return date.toISOString().split('T')[0]; }; // Determine query based on filter const isQuestionPeriod = filter === 'qp'; const documentType = filter === 'debates' ? 'D' : filter === 'committee' ? 'E' : null; // Query const { data, loading, error } = useQuery( isQuestionPeriod ? GET_QUESTION_PERIOD_DEBATES : GET_RECENT_DEBATES, { variables: isQuestionPeriod ? { limit } : { limit, documentType, questionPeriodOnly: false } } ); const debates = isQuestionPeriod ? data?.questionPeriodDebates || [] : data?.recentDebates || []; // Search filtering const filteredDebates = useMemo(() => { if (!searchQuery.trim()) return debates; const query = searchQuery.toLowerCase(); return debates.filter((debate: any) => { // Search in top topics const topicsMatch = debate.top_topics?.some((topic: string) => topic.toLowerCase().includes(query) ); // Search in metadata const typeMatch = debate.document.document_type?.toLowerCase().includes(query); const sessionMatch = debate.document.session_id?.toLowerCase().includes(query); // Search in keywords (when available) const keywordsJson = locale === 'fr' ? debate.document.keywords_fr : debate.document.keywords_en; let keywordsMatch = false; if (keywordsJson) { try { const keywords = JSON.parse(keywordsJson); keywordsMatch = keywords.some((kw: any) => kw.word.toLowerCase().includes(query) ); } catch {} } return topicsMatch || typeMatch || sessionMatch || keywordsMatch; }); }, [debates, searchQuery, locale]); return ( <> <Header /> <div className="min-h-screen bg-bg-base"> {/* Header */} <div className="bg-bg-elevated border-b border-border-subtle"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div className="flex items-center gap-3 mb-4"> <Calendar className="h-8 w-8 text-accent-red" /> <h1 className="text-3xl font-bold text-text-primary"> {locale === 'fr' ? 'Débats parlementaires' : 'Parliamentary Debates'} </h1> </div> <p className="text-lg text-text-secondary max-w-3xl"> {locale === 'fr' ? 'Explorez les débats récents de la Chambre des communes et les témoignages des comités' : 'Explore recent House of Commons debates and committee testimony'} </p> </div> </div> {/* Conditional Layout: Side-by-side (1 month) or Stacked (3 months) */} {calendarViewMode === 1 ? ( /* 1-MONTH VIEW: Side-by-side layout */ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> {/* Calendar Sidebar (1/3) */} <div className="lg:col-span-1"> <div className="sticky top-4"> <DebatesCalendar onDateRangeSelect={handleDateRangeSelect} selectedStartDate={selectedStartDate} selectedEndDate={selectedEndDate} onViewModeChange={handleViewModeChange} /> </div> </div> {/* Content Area (2/3) */} <div className="lg:col-span-2"> {/* Filters */} <div className="flex items-center gap-3 mb-6"> <Filter className="h-5 w-5 text-text-tertiary" /> <div className="flex gap-2 flex-wrap"> <button onClick={() => setFilter('all')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'all' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Tous' : 'All'} </button> <button onClick={() => setFilter('debates')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'debates' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Débats de la Chambre' : 'House Debates'} </button> <button onClick={() => setFilter('committee')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'committee' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Comités' : 'Committee'} </button> <button onClick={() => setFilter('qp')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'qp' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Période des questions' : 'Question Period'} </button> </div> </div> {/* Search Bar */} <div className="mb-6"> <div className="relative"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-text-tertiary" /> <input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder={locale === 'fr' ? 'Rechercher par sujet, type, mots-clés...' : 'Search by topic, type, keywords...'} className="w-full pl-10 pr-10 py-3 bg-bg-elevated border border-border-subtle rounded-lg text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-red focus:border-transparent transition-all" /> {searchQuery && ( <button onClick={() => setSearchQuery('')} className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-bg-overlay rounded transition-colors" aria-label="Clear search" > <X className="h-4 w-4 text-text-tertiary" /> </button> )} </div> </div> {/* Loading State */} {loading && ( <div className="text-center py-12"> <div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-accent-red"></div> <p className="mt-4 text-text-secondary"> {locale === 'fr' ? 'Chargement des débats...' : 'Loading debates...'} </p> </div> )} {/* Error State */} {error && ( <div className="bg-bg-elevated border border-border-subtle rounded-lg p-6 text-center"> <p className="text-text-secondary"> {locale === 'fr' ? 'Erreur lors du chargement des débats. Veuillez réessayer.' : 'Error loading debates. Please try again.'} </p> <p className="text-sm text-text-tertiary mt-2">{error.message}</p> </div> )} {/* Debates List */} {!loading && !error && ( <> {filteredDebates.length === 0 ? ( <div className="bg-bg-elevated border border-border-subtle rounded-lg p-12 text-center"> <Calendar className="h-16 w-16 text-text-tertiary mx-auto mb-4" /> <p className="text-lg text-text-secondary"> {locale === 'fr' ? 'Aucun débat trouvé pour ce filtre.' : 'No debates found for this filter.'} </p> </div> ) : ( <div className="space-y-4"> {/* Results count */} <div className="text-sm text-text-tertiary"> {locale === 'fr' ? `${filteredDebates.length} ${filteredDebates.length === 1 ? 'débat' : 'débats'}` : `${filteredDebates.length} ${filteredDebates.length === 1 ? 'debate' : 'debates'}`} </div> {/* Debate cards */} {filteredDebates.map((debate: any) => ( <DebateCard key={debate.document.id} debate={debate} /> ))} </div> )} </> )} </div> </div> </div> ) : ( /* 3-MONTH VIEW: Stacked vertical layout */ <> {/* Calendar at top */} <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <DebatesCalendar onDateRangeSelect={handleDateRangeSelect} selectedStartDate={selectedStartDate} selectedEndDate={selectedEndDate} onViewModeChange={handleViewModeChange} /> </div> {/* Content below */} <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> {/* Filters */} <div className="flex items-center gap-3 mb-6"> <Filter className="h-5 w-5 text-text-tertiary" /> <div className="flex gap-2 flex-wrap"> <button onClick={() => setFilter('all')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'all' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Tous' : 'All'} </button> <button onClick={() => setFilter('debates')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'debates' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Débats de la Chambre' : 'House Debates'} </button> <button onClick={() => setFilter('committee')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'committee' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Comités' : 'Committee'} </button> <button onClick={() => setFilter('qp')} className={`px-4 py-2 rounded-lg font-medium transition-colors ${ filter === 'qp' ? 'bg-accent-red text-white' : 'bg-bg-elevated text-text-secondary hover:bg-bg-overlay border border-border-subtle' }`} > {locale === 'fr' ? 'Période des questions' : 'Question Period'} </button> </div> </div> {/* Search Bar */} <div className="mb-6"> <div className="relative"> <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 text-text-tertiary" /> <input type="text" value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} placeholder={locale === 'fr' ? 'Rechercher par sujet, type, mots-clés...' : 'Search by topic, type, keywords...'} className="w-full pl-10 pr-10 py-3 bg-bg-elevated border border-border-subtle rounded-lg text-text-primary placeholder:text-text-tertiary focus:outline-none focus:ring-2 focus:ring-accent-red focus:border-transparent transition-all" /> {searchQuery && ( <button onClick={() => setSearchQuery('')} className="absolute right-3 top-1/2 transform -translate-y-1/2 p-1 hover:bg-bg-overlay rounded transition-colors" aria-label="Clear search" > <X className="h-4 w-4 text-text-tertiary" /> </button> )} </div> </div> {/* Loading State */} {loading && ( <div className="text-center py-12"> <div className="inline-block animate-spin rounded-full h-12 w-12 border-b-2 border-accent-red"></div> <p className="mt-4 text-text-secondary"> {locale === 'fr' ? 'Chargement des débats...' : 'Loading debates...'} </p> </div> )} {/* Error State */} {error && ( <div className="bg-bg-elevated border border-border-subtle rounded-lg p-6 text-center"> <p className="text-text-secondary"> {locale === 'fr' ? 'Erreur lors du chargement des débats. Veuillez réessayer.' : 'Error loading debates. Please try again.'} </p> <p className="text-sm text-text-tertiary mt-2">{error.message}</p> </div> )} {/* Debates List */} {!loading && !error && ( <> {filteredDebates.length === 0 ? ( <div className="bg-bg-elevated border border-border-subtle rounded-lg p-12 text-center"> <Calendar className="h-16 w-16 text-text-tertiary mx-auto mb-4" /> <p className="text-lg text-text-secondary"> {locale === 'fr' ? 'Aucun débat trouvé pour ce filtre.' : 'No debates found for this filter.'} </p> </div> ) : ( <div className="space-y-4"> {/* Results count */} <div className="text-sm text-text-tertiary"> {locale === 'fr' ? `${filteredDebates.length} ${filteredDebates.length === 1 ? 'débat' : 'débats'}` : `${filteredDebates.length} ${filteredDebates.length === 1 ? 'debate' : 'debates'}`} </div> {/* Debate cards */} {filteredDebates.map((debate: any) => ( <DebateCard key={debate.document.id} debate={debate} /> ))} </div> )} </> )} </div> </> )} </div> <Footer /> </> ); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/northernvariables/FedMCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server